#include "game-init.h"
#include "imgui.h"
#include "imgui_impl_dx12.h"
#include "imgui_impl_win32.h"
#include "mgr.h"
#include "tab1.h"
#include "tab2.h"
#include "tab3.h"
#include <Windows.h>
#include <d3d12.h>
#include <dxgi1_5.h>
#include <tchar.h>
// #include <thread>

// extern std::atomic<bool> keep_thread_running;
// extern std::thread fishing_thread;

// Our state
static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
static bool open = true;
ImVec2 btn_size = ImVec2(200, 60);

ImVec4 success_color = ImVec4(0.2, 0.8, 0.2, 1.0);
ImVec4 info_color = ImVec4(1.0f, 0.5f, 0.0f, 1.0f);
ImVec4 warning_color = ImVec4(1.0f, 1.0f, 0.0f, 1.0f);
ImVec4 error_color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);

bool render() {
  // Start the Dear ImGui frame
  ImGui_ImplDX12_NewFrame();
  ImGui_ImplWin32_NewFrame();
  ImGui::NewFrame();

  bool done = false;
  if (open) {
    ImGui::SetNextWindowPos(ImVec2{700, 500}, ImGuiCond_FirstUseEver);
    ImGui::SetNextWindowSize(ImVec2{800, 600}, ImGuiCond_FirstUseEver);
    ImGui::Begin("Classic 泰坦重铸", &open);
    // Tab
    if (ImGui::BeginTabBar("Tab")) {
      if (ImGui::BeginTabItem("主界面")) {
        tab1();
        ImGui::EndTabItem();
      }
      if (ImGui::BeginTabItem("第2页")) {
        tab2();
        ImGui::EndTabItem();
      }
      if (ImGui::BeginTabItem("第3页")) {
        tab3();
        ImGui::EndTabItem();
      }
      ImGui::EndTabBar();
    }
    ImGui::End();
  } else {
    done = true;
  }
  // Rendering
  ImGui::Render();
  return done;
}

DWORD WINAPI imgui_routine(LPVOID param);

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,
                      LPVOID lpReserved) {
  switch (ul_reason_for_call) {
  case DLL_PROCESS_ATTACH: {
    ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)imgui_routine, 0, 0, 0);
    break;
  }
  case DLL_THREAD_ATTACH:
  case DLL_THREAD_DETACH:
  case DLL_PROCESS_DETACH:
    break;
  }
  return TRUE;
}

// Config for example app
static const int APP_NUM_FRAMES_IN_FLIGHT = 2;
static const int APP_NUM_BACK_BUFFERS = 2;
static const int APP_SRV_HEAP_SIZE = 64;

struct FrameContext {
  ID3D12CommandAllocator *CommandAllocator;
  UINT64 FenceValue;
};

// Simple free list based allocator
struct ExampleDescriptorHeapAllocator {
  ID3D12DescriptorHeap *Heap = nullptr;
  D3D12_DESCRIPTOR_HEAP_TYPE HeapType = D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES;
  D3D12_CPU_DESCRIPTOR_HANDLE HeapStartCpu;
  D3D12_GPU_DESCRIPTOR_HANDLE HeapStartGpu;
  UINT HeapHandleIncrement;
  ImVector<int> FreeIndices;

  void Create(ID3D12Device *device, ID3D12DescriptorHeap *heap) {
    IM_ASSERT(Heap == nullptr && FreeIndices.empty());
    Heap = heap;
    D3D12_DESCRIPTOR_HEAP_DESC desc = heap->GetDesc();
    HeapType = desc.Type;
    HeapStartCpu = Heap->GetCPUDescriptorHandleForHeapStart();
    HeapStartGpu = Heap->GetGPUDescriptorHandleForHeapStart();
    HeapHandleIncrement = device->GetDescriptorHandleIncrementSize(HeapType);
    FreeIndices.reserve((int)desc.NumDescriptors);
    for (int n = desc.NumDescriptors; n > 0; n--)
      FreeIndices.push_back(n - 1);
  }
  void Destroy() {
    Heap = nullptr;
    FreeIndices.clear();
  }
  void Alloc(D3D12_CPU_DESCRIPTOR_HANDLE *out_cpu_desc_handle,
             D3D12_GPU_DESCRIPTOR_HANDLE *out_gpu_desc_handle) {
    IM_ASSERT(FreeIndices.Size > 0);
    int idx = FreeIndices.back();
    FreeIndices.pop_back();
    out_cpu_desc_handle->ptr = HeapStartCpu.ptr + (idx * HeapHandleIncrement);
    out_gpu_desc_handle->ptr = HeapStartGpu.ptr + (idx * HeapHandleIncrement);
  }
  void Free(D3D12_CPU_DESCRIPTOR_HANDLE out_cpu_desc_handle,
            D3D12_GPU_DESCRIPTOR_HANDLE out_gpu_desc_handle) {
    int cpu_idx = (int)((out_cpu_desc_handle.ptr - HeapStartCpu.ptr) /
                        HeapHandleIncrement);
    int gpu_idx = (int)((out_gpu_desc_handle.ptr - HeapStartGpu.ptr) /
                        HeapHandleIncrement);
    IM_ASSERT(cpu_idx == gpu_idx);
    FreeIndices.push_back(cpu_idx);
  }
};

// Data
static FrameContext g_frameContext[APP_NUM_FRAMES_IN_FLIGHT] = {};
static UINT g_frameIndex = 0;

static ID3D12Device *g_pd3dDevice = nullptr;
static ID3D12DescriptorHeap *g_pd3dRtvDescHeap = nullptr;
static ID3D12DescriptorHeap *g_pd3dSrvDescHeap = nullptr;
static ExampleDescriptorHeapAllocator g_pd3dSrvDescHeapAlloc;
static ID3D12CommandQueue *g_pd3dCommandQueue = nullptr;
static ID3D12GraphicsCommandList *g_pd3dCommandList = nullptr;
static ID3D12Fence *g_fence = nullptr;
static HANDLE g_fenceEvent = nullptr;
static UINT64 g_fenceLastSignaledValue = 0;
static IDXGISwapChain3 *g_pSwapChain = nullptr;
static bool g_SwapChainTearingSupport = false;
static bool g_SwapChainOccluded = false;
static HANDLE g_hSwapChainWaitableObject = nullptr;
static ID3D12Resource *g_mainRenderTargetResource[APP_NUM_BACK_BUFFERS] = {};
static D3D12_CPU_DESCRIPTOR_HANDLE
    g_mainRenderTargetDescriptor[APP_NUM_BACK_BUFFERS] = {};

// Forward declarations of helper functions
bool CreateDeviceD3D(HWND hWnd);
void CleanupDeviceD3D();
void CreateRenderTarget();
void CleanupRenderTarget();
void WaitForPendingOperations();
FrameContext *WaitForNextFrameContext();
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// 应用程序入口点
// int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
//                    LPSTR lpCmdLine, int nCmdShow) {

DWORD WINAPI imgui_routine(LPVOID param) {
  // Make process DPI aware and obtain main monitor scale
  ImGui_ImplWin32_EnableDpiAwareness();
  float main_scale = ImGui_ImplWin32_GetDpiScaleForMonitor(
      ::MonitorFromPoint(POINT{0, 0}, MONITOR_DEFAULTTOPRIMARY));

  // Create application window
  WNDCLASSEXW wc = {sizeof(wc),
                    CS_CLASSDC,
                    WndProc,
                    0L,
                    0L,
                    GetModuleHandle(nullptr),
                    nullptr,
                    nullptr,
                    nullptr,
                    nullptr,
                    L"ImGui Example",
                    nullptr};
  ::RegisterClassExW(&wc);
  HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX12 Example",
                              WS_OVERLAPPEDWINDOW, 100, 100,
                              (int)(1280 * main_scale), (int)(800 * main_scale),
                              nullptr, nullptr, wc.hInstance, nullptr);

  // Initialize Direct3D
  if (!CreateDeviceD3D(hwnd)) {
    CleanupDeviceD3D();
    ::UnregisterClassW(wc.lpszClassName, wc.hInstance);
    return 1;
  }

  // Show the window
  // ::ShowWindow(hwnd, SW_SHOWDEFAULT);
  ::ShowWindow(hwnd, SW_HIDE);
  ::UpdateWindow(hwnd);

  // Setup Dear ImGui context
  IMGUI_CHECKVERSION();
  ImGui::CreateContext();
  ImGuiIO &io = ImGui::GetIO();
  (void)io;
  io.ConfigFlags |=
      ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
  io.ConfigFlags |=
      ImGuiConfigFlags_NavEnableGamepad;              // Enable Gamepad Controls
  io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;   // Enable Docking
  io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport /
                                                      // Platform Windows
  // 这个配置就是让 imgui 窗口可以脱离主窗口独立显示.
  // 现在可以将主窗口隐藏了, ::ShowWindow(hwnd, SW_HIDE);
  io.ConfigViewportsNoAutoMerge = true;
  // io.ConfigViewportsNoTaskBarIcon = true;

  // Setup Dear ImGui style
  ImGui::StyleColorsDark();
  // ImGui::StyleColorsLight();

  // Setup scaling
  ImGuiStyle &style = ImGui::GetStyle();
  style.ScaleAllSizes(
      main_scale); // Bake a fixed style scale. (until we have a solution for
                   // dynamic style scaling, changing this requires resetting
                   // Style + calling this again)
  style.FontScaleDpi =
      main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true
                  // makes this unnecessary. We leave both here for
                  // documentation purpose)
  io.ConfigDpiScaleFonts =
      true; // [Experimental] Automatically overwrite style.FontScaleDpi in
            // Begin() when Monitor DPI changes. This will scale fonts but _NOT_
            // scale sizes/padding for now.
  io.ConfigDpiScaleViewports =
      true; // [Experimental] Scale Dear ImGui and Platform Windows when Monitor
            // DPI changes.

  // When viewports are enabled we tweak WindowRounding/WindowBg so platform
  // windows can look identical to regular ones.
  if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
    style.WindowRounding = 0.0f;
    style.Colors[ImGuiCol_WindowBg].w = 1.0f;
  }

  // Setup Platform/Renderer backends
  ImGui_ImplWin32_Init(hwnd);

  ImGui_ImplDX12_InitInfo init_info = {};
  init_info.Device = g_pd3dDevice;
  init_info.CommandQueue = g_pd3dCommandQueue;
  init_info.NumFramesInFlight = APP_NUM_FRAMES_IN_FLIGHT;
  init_info.RTVFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
  init_info.DSVFormat = DXGI_FORMAT_UNKNOWN;
  // Allocating SRV descriptors (for textures) is up to the application, so we
  // provide callbacks. (current version of the backend will only allocate one
  // descriptor, future versions will need to allocate more)
  init_info.SrvDescriptorHeap = g_pd3dSrvDescHeap;
  init_info.SrvDescriptorAllocFn =
      [](ImGui_ImplDX12_InitInfo *, D3D12_CPU_DESCRIPTOR_HANDLE *out_cpu_handle,
         D3D12_GPU_DESCRIPTOR_HANDLE *out_gpu_handle) {
        return g_pd3dSrvDescHeapAlloc.Alloc(out_cpu_handle, out_gpu_handle);
      };
  init_info.SrvDescriptorFreeFn = [](ImGui_ImplDX12_InitInfo *,
                                     D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle,
                                     D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle) {
    return g_pd3dSrvDescHeapAlloc.Free(cpu_handle, gpu_handle);
  };
  ImGui_ImplDX12_Init(&init_info);

  // 中文支持
  ImFont *font = io.Fonts->AddFontFromFileTTF(
      "c:\\Windows\\Fonts\\msyh.ttc", 28.0f, nullptr,
      io.Fonts->GetGlyphRangesChineseFull());
  IM_ASSERT(font != nullptr);

  game_init();

  Mgr::get()->game()->hook_main_thread();

  // Main loop
  bool done = false;
  while (!done) {
    // Poll and handle messages (inputs, window resize, etc.)
    // See the WndProc() function below for our to dispatch events to the Win32
    // backend.
    MSG msg;
    while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
      ::TranslateMessage(&msg);
      ::DispatchMessage(&msg);
      if (msg.message == WM_QUIT)
        done = true;
    }
    if (done)
      break;

    // Handle window screen locked
    if ((g_SwapChainOccluded &&
         g_pSwapChain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED) ||
        ::IsIconic(hwnd)) {
      ::Sleep(10);
      continue;
    }
    g_SwapChainOccluded = false;

    done = render();

    FrameContext *frameCtx = WaitForNextFrameContext();
    UINT backBufferIdx = g_pSwapChain->GetCurrentBackBufferIndex();
    frameCtx->CommandAllocator->Reset();

    D3D12_RESOURCE_BARRIER barrier = {};
    barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
    barrier.Transition.pResource = g_mainRenderTargetResource[backBufferIdx];
    barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
    barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
    barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
    g_pd3dCommandList->Reset(frameCtx->CommandAllocator, nullptr);
    g_pd3dCommandList->ResourceBarrier(1, &barrier);

    // Render Dear ImGui graphics
    const float clear_color_with_alpha[4] = {
        clear_color.x * clear_color.w, clear_color.y * clear_color.w,
        clear_color.z * clear_color.w, clear_color.w};
    g_pd3dCommandList->ClearRenderTargetView(
        g_mainRenderTargetDescriptor[backBufferIdx], clear_color_with_alpha, 0,
        nullptr);
    g_pd3dCommandList->OMSetRenderTargets(
        1, &g_mainRenderTargetDescriptor[backBufferIdx], FALSE, nullptr);
    g_pd3dCommandList->SetDescriptorHeaps(1, &g_pd3dSrvDescHeap);
    ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), g_pd3dCommandList);
    barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
    barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
    g_pd3dCommandList->ResourceBarrier(1, &barrier);
    g_pd3dCommandList->Close();

    g_pd3dCommandQueue->ExecuteCommandLists(
        1, (ID3D12CommandList *const *)&g_pd3dCommandList);

    // Update and Render additional Platform Windows
    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
      ImGui::UpdatePlatformWindows();
      ImGui::RenderPlatformWindowsDefault();
    }

    g_pd3dCommandQueue->Signal(g_fence, ++g_fenceLastSignaledValue);
    frameCtx->FenceValue = g_fenceLastSignaledValue;

    // Present
    HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync
    // HRESULT hr = g_pSwapChain->Present(0, g_SwapChainTearingSupport ?
    // DXGI_PRESENT_ALLOW_TEARING : 0); // Present without vsync
    g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED);
    g_frameIndex++;
  }

  Mgr::get()->game()->unhook_main_thread();

  // keep_thread_running = false;
  // if (fishing_thread.joinable()) {
  //   fishing_thread.join();
  // }

  WaitForPendingOperations();

  // Cleanup
  ImGui_ImplDX12_Shutdown();
  ImGui_ImplWin32_Shutdown();
  ImGui::DestroyContext();

  CleanupDeviceD3D();
  ::DestroyWindow(hwnd);
  ::UnregisterClassW(wc.lpszClassName, wc.hInstance);

  return 0;
}

// Helper functions
bool CreateDeviceD3D(HWND hWnd) {
  // Setup swap chain
  DXGI_SWAP_CHAIN_DESC1 sd;
  {
    ZeroMemory(&sd, sizeof(sd));
    sd.BufferCount = APP_NUM_BACK_BUFFERS;
    sd.Width = 0;
    sd.Height = 0;
    sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    sd.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
    sd.Scaling = DXGI_SCALING_STRETCH;
    sd.Stereo = FALSE;
  }

  // [DEBUG] Enable debug interface
#ifdef DX12_ENABLE_DEBUG_LAYER
  ID3D12Debug *pdx12Debug = nullptr;
  if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&pdx12Debug))))
    pdx12Debug->EnableDebugLayer();
#endif

  // Create device
  D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0;
  if (D3D12CreateDevice(nullptr, featureLevel, IID_PPV_ARGS(&g_pd3dDevice)) !=
      S_OK)
    return false;

  // [DEBUG] Setup debug interface to break on any warnings/errors
#ifdef DX12_ENABLE_DEBUG_LAYER
  if (pdx12Debug != nullptr) {
    ID3D12InfoQueue *pInfoQueue = nullptr;
    g_pd3dDevice->QueryInterface(IID_PPV_ARGS(&pInfoQueue));
    pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true);
    pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true);
    pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true);
    pInfoQueue->Release();
    pdx12Debug->Release();
  }
#endif

  {
    D3D12_DESCRIPTOR_HEAP_DESC desc = {};
    desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
    desc.NumDescriptors = APP_NUM_BACK_BUFFERS;
    desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    desc.NodeMask = 1;
    if (g_pd3dDevice->CreateDescriptorHeap(
            &desc, IID_PPV_ARGS(&g_pd3dRtvDescHeap)) != S_OK)
      return false;

    SIZE_T rtvDescriptorSize = g_pd3dDevice->GetDescriptorHandleIncrementSize(
        D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
    D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle =
        g_pd3dRtvDescHeap->GetCPUDescriptorHandleForHeapStart();
    for (UINT i = 0; i < APP_NUM_BACK_BUFFERS; i++) {
      g_mainRenderTargetDescriptor[i] = rtvHandle;
      rtvHandle.ptr += rtvDescriptorSize;
    }
  }

  {
    D3D12_DESCRIPTOR_HEAP_DESC desc = {};
    desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
    desc.NumDescriptors = APP_SRV_HEAP_SIZE;
    desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
    if (g_pd3dDevice->CreateDescriptorHeap(
            &desc, IID_PPV_ARGS(&g_pd3dSrvDescHeap)) != S_OK)
      return false;
    g_pd3dSrvDescHeapAlloc.Create(g_pd3dDevice, g_pd3dSrvDescHeap);
  }

  {
    D3D12_COMMAND_QUEUE_DESC desc = {};
    desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    desc.NodeMask = 1;
    if (g_pd3dDevice->CreateCommandQueue(
            &desc, IID_PPV_ARGS(&g_pd3dCommandQueue)) != S_OK)
      return false;
  }

  for (UINT i = 0; i < APP_NUM_FRAMES_IN_FLIGHT; i++)
    if (g_pd3dDevice->CreateCommandAllocator(
            D3D12_COMMAND_LIST_TYPE_DIRECT,
            IID_PPV_ARGS(&g_frameContext[i].CommandAllocator)) != S_OK)
      return false;

  if (g_pd3dDevice->CreateCommandList(
          0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_frameContext[0].CommandAllocator,
          nullptr, IID_PPV_ARGS(&g_pd3dCommandList)) != S_OK ||
      g_pd3dCommandList->Close() != S_OK)
    return false;

  if (g_pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE,
                                IID_PPV_ARGS(&g_fence)) != S_OK)
    return false;

  g_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
  if (g_fenceEvent == nullptr)
    return false;

  {
    IDXGIFactory5 *dxgiFactory = nullptr;
    IDXGISwapChain1 *swapChain1 = nullptr;
    if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK)
      return false;

    BOOL allow_tearing = FALSE;
    dxgiFactory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING,
                                     &allow_tearing, sizeof(allow_tearing));
    g_SwapChainTearingSupport = (allow_tearing == TRUE);
    if (g_SwapChainTearingSupport)
      sd.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;

    if (dxgiFactory->CreateSwapChainForHwnd(g_pd3dCommandQueue, hWnd, &sd,
                                            nullptr, nullptr,
                                            &swapChain1) != S_OK)
      return false;
    if (swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain)) != S_OK)
      return false;
    if (g_SwapChainTearingSupport)
      dxgiFactory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER);

    swapChain1->Release();
    dxgiFactory->Release();
    g_pSwapChain->SetMaximumFrameLatency(APP_NUM_BACK_BUFFERS);
    g_hSwapChainWaitableObject = g_pSwapChain->GetFrameLatencyWaitableObject();
  }

  CreateRenderTarget();
  return true;
}

void CleanupDeviceD3D() {
  CleanupRenderTarget();
  if (g_pSwapChain) {
    g_pSwapChain->SetFullscreenState(false, nullptr);
    g_pSwapChain->Release();
    g_pSwapChain = nullptr;
  }
  if (g_hSwapChainWaitableObject != nullptr) {
    CloseHandle(g_hSwapChainWaitableObject);
  }
  for (UINT i = 0; i < APP_NUM_FRAMES_IN_FLIGHT; i++)
    if (g_frameContext[i].CommandAllocator) {
      g_frameContext[i].CommandAllocator->Release();
      g_frameContext[i].CommandAllocator = nullptr;
    }
  if (g_pd3dCommandQueue) {
    g_pd3dCommandQueue->Release();
    g_pd3dCommandQueue = nullptr;
  }
  if (g_pd3dCommandList) {
    g_pd3dCommandList->Release();
    g_pd3dCommandList = nullptr;
  }
  if (g_pd3dRtvDescHeap) {
    g_pd3dRtvDescHeap->Release();
    g_pd3dRtvDescHeap = nullptr;
  }
  if (g_pd3dSrvDescHeap) {
    g_pd3dSrvDescHeap->Release();
    g_pd3dSrvDescHeap = nullptr;
  }
  if (g_fence) {
    g_fence->Release();
    g_fence = nullptr;
  }
  if (g_fenceEvent) {
    CloseHandle(g_fenceEvent);
    g_fenceEvent = nullptr;
  }
  if (g_pd3dDevice) {
    g_pd3dDevice->Release();
    g_pd3dDevice = nullptr;
  }

#ifdef DX12_ENABLE_DEBUG_LAYER
  IDXGIDebug1 *pDebug = nullptr;
  if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(&pDebug)))) {
    pDebug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_SUMMARY);
    pDebug->Release();
  }
#endif
}

void CreateRenderTarget() {
  for (UINT i = 0; i < APP_NUM_BACK_BUFFERS; i++) {
    ID3D12Resource *pBackBuffer = nullptr;
    g_pSwapChain->GetBuffer(i, IID_PPV_ARGS(&pBackBuffer));
    g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr,
                                         g_mainRenderTargetDescriptor[i]);
    g_mainRenderTargetResource[i] = pBackBuffer;
  }
}

void CleanupRenderTarget() {
  WaitForPendingOperations();

  for (UINT i = 0; i < APP_NUM_BACK_BUFFERS; i++)
    if (g_mainRenderTargetResource[i]) {
      g_mainRenderTargetResource[i]->Release();
      g_mainRenderTargetResource[i] = nullptr;
    }
}

void WaitForPendingOperations() {
  g_pd3dCommandQueue->Signal(g_fence, ++g_fenceLastSignaledValue);

  g_fence->SetEventOnCompletion(g_fenceLastSignaledValue, g_fenceEvent);
  ::WaitForSingleObject(g_fenceEvent, INFINITE);
}

FrameContext *WaitForNextFrameContext() {
  FrameContext *frame_context =
      &g_frameContext[g_frameIndex % APP_NUM_FRAMES_IN_FLIGHT];
  if (g_fence->GetCompletedValue() < frame_context->FenceValue) {
    g_fence->SetEventOnCompletion(frame_context->FenceValue, g_fenceEvent);
    HANDLE waitableObjects[] = {g_hSwapChainWaitableObject, g_fenceEvent};
    ::WaitForMultipleObjects(2, waitableObjects, TRUE, INFINITE);
  } else
    ::WaitForSingleObject(g_hSwapChainWaitableObject, INFINITE);

  return frame_context;
}

// Forward declare message handler from imgui_impl_win32.cpp
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd,
                                                             UINT msg,
                                                             WPARAM wParam,
                                                             LPARAM lParam);

// Win32 message handler
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if
// dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your
// main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to
// your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from
// your application based on those two flags.
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
  if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
    return true;

  switch (msg) {
  case WM_SIZE:
    if (g_pd3dDevice != nullptr && wParam != SIZE_MINIMIZED) {
      CleanupRenderTarget();
      DXGI_SWAP_CHAIN_DESC1 desc = {};
      g_pSwapChain->GetDesc1(&desc);
      HRESULT result = g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam),
                                                   (UINT)HIWORD(lParam),
                                                   desc.Format, desc.Flags);
      assert(SUCCEEDED(result) && "Failed to resize swapchain.");
      CreateRenderTarget();
    }
    return 0;
  case WM_SYSCOMMAND:
    if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu
      return 0;
    break;
  case WM_DESTROY:
    ::PostQuitMessage(0);
    return 0;
  }
  return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}
